Effective-Objective-C读书笔记(3)

接口与 API 设计

第15条:用前缀避免命名空间冲突

选择你的公司、应用程序或二者皆有关联的名称作为类的前缀,并在所有的代码中均使用这一前缀。

程序中使用到第三方库,则应为其中的名称加上前缀。如果命名出现冲突,会出现以下错误:

1
2
3
duplicate symbol _OBJC_METACLASS_$_***Class in:
...
...

第16条:提供“指定初始化方法”

在类当中提供一个指定初始化方法,并与文档里面说明,其他初始化方法均应该调用此方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// JYRectangle.h
@interface JYRectangle : NSObject<NSCoding>
@property (nonatomic, assign, readonly) float width;
@property (nonatomic, assign, readonly) float height;

- (id)initWithWidth:(float)width
andHeight:(float)height;
- (void)methodMustBeOverride;
@end

// JYRectangle.m
@implementation JYRectangle
- (id)initWithWidth:(float)width andHeight:(float)height {
if (self = [super init]) {
_width = width;
_height = height;
}
return self;
}

- (instancetype)init {
return [self initWithWidth:5.0 andHeight:5.0];
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
_width = [aDecoder decodeFloatForKey:@"width"];
_height = [aDecoder decodeFloatForKey:@"height"];
}
return self;
}

- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeFloat:_width forKey:@"width"];
[aCoder encodeFloat:_height forKey:@"height"];
}

- (void)methodMustBeOverride {
@throw [NSException exceptionWithName:NSInternalInconsistencyException
reason:@"This method must be override !"
userInfo:nil];
}

@end

如果子类想要提供不同的函数实现,需要覆写父类的方法。父类不提供实现的时候,在其中抛出异常,强制子类覆写此方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

// JYSquare.h
@interface JYSquare : JYRectangle
- (id)initWithDimension:(float)dimension;
@end


// JYSquare.m
@implementation JYSquare
- (id)initWithDimension:(float)dimension {
return [super initWithWidth:dimension andHeight:dimension];
}

- (id)initWithWidth:(float)width andHeight:(float)height {
float dimension = MAX(width, height);
return [self initWithDimension:dimension];
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if ([super initWithCoder:aDecoder]) {
// JYSquare's specific initializer
}
return self;
}

- (void)methodMustBeOverride {
// If subclass is not override this method, will throw exception
}
@end

第17条:实现 description 方法

实现 description 方法返回一个有意义的字符串,用来描述实例,也可以通过 debugDescription 方法返回更详细的信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
- (NSString *)description {
return [NSString stringWithFormat:@"<%@: %p, %@>",
[self class],
self,
@{@"title", _title,
@"name", _name,}
];
}

// 只有在控制台使用 LLDB 的 po 命令有效
- (NSString *)debugDescription {
return [NSString stringWithFormat:@"<%@: %p, %@>", [self class], self, _title];
}

第18条:尽量使用不可变对象

  • 将对外公布的属性设置为 readonly,在匿名分类中将属性重新定义为readwrite。即使属性被声明为readonly,然而在对象外部,还可以通过 KVC 技术来设置属性的值。比如[someProperty setValue:@"abc" forKey:@"someValue"];,KVC 会在类中查找 setSomeProperty: 方法,我们最好不要通过这种 hack 方式来设置属性,不然会造成莫名的问题。
  • 尽量创建不可变对象。
  • 不要将 collection 作为属性公开,而是应该提供相关方法,以此修改对象中的不可变对象。

第19条:清晰的命名

  • 继承自类,命名应该与类保持一致,比如从 UIView 中继承的子类,其末尾的词必须是个 View。
  • 方法和变量名都使用驼峰式命名。
  • 保持与自己代码或集成框架代码相符。

第20条:为私有方法添加前缀

  • 私有方法建议使用 p_privateMethodName 开头,与公共方法区分开。
  • OC 中没有办法标记私有方法,所有根据这一条命名私有方法,增强代码可读性。
  • 不要单用下划线作为私有方法的前缀,这种是预留给苹果公司使用的。

第21条:Objective-C 中的错误类型

  • 异常只有程序发生严重错误时候才能抛出,比如定义一个抽象类,子类必须覆写父类的方法,可以在父类的方法里面抛出异常。这样子类如果没有覆写父类方法,就会抛出异常。
  • 其他非致命的错误,Objective-C 中所用的编程范式为:令方法返回 nil/0,或者使用 NSError。
  • 收到 NSError 消息时,必须优先处理错误。

第21条:NSCopying 协议

自己的类想要支持 copy 操作,必须要实现 NSCopying 协议。该协议有以下方法:

1
2
// NSZone:以前开发程序会把内存分成不同的区,对象存在某个区里面。现在每个程序只有一个区:默认区,不用担心 zone 参数
- (id)copyWithZone:(nullable NSZone *)zone;
  • 复制对象时决定采用深拷贝韩式浅拷贝,一般情况下应该尽量执行浅拷贝。
  • 自定义对象分为可变和不可变版本,需要同时实现 NSCopyingNSMutableCopying